/**
 *    Copyright (C) 2007-2009 DIVIS GmbH (http://www.divis-hagen.de/)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 3 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General
 *    Public License along with this library; if not, write to the
 *    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *    Boston, MA 02110-1301 USA
 */
package de.divis_hagen.json.rs.client;

import de.divis_hagen.json.JSONContextImpl;
import de.divis_hagen.json.JSONException;
import de.divis_hagen.json.JSONObject;
import de.divis_hagen.json.JSONTree;
import de.divis_hagen.json.TreeBuilder;
import de.divis_hagen.json.annotation.EntityType;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


/**
 * <p>
 * Create JavaScript-client-files from JAX-RS and JAXB classes.
 * </p>
 *
 * @author Aike J Sommer
 */
public class JSCompiler {

    private final String basePackage;

    private final boolean debug;

    private final Set<Class> handlerClasses = new HashSet<Class>();

    private final Map<Class, Class> entityClasses = new HashMap<Class, Class>();

    private final JSONContextImpl context = new JSONContextImpl();

    private final TreeBuilder builder = new TreeBuilder(context);

    private int indent = 0;

    private Writer out;

    public JSCompiler(String basePackage, Collection<Class> handlerClasses, boolean debug) {
        this.basePackage = basePackage;
        this.debug = debug;
        this.handlerClasses.addAll(handlerClasses);

        resolveClasses();
    }

    private void resolveClasses() {
        for (Class c : handlerClasses) {
            if (c.isAnnotationPresent(EntityType.class)) {
                entityClasses.put(((EntityType) c.getAnnotation(EntityType.class)).value(), c);
            }
        }
    }

    public void generate(OutputStream out) throws IOException, JSONException {
        generate(new OutputStreamWriter(out));
    }

    public void generate(Writer out) throws IOException, JSONException {
        Collection<EntityInfo> entityInfos = loadInfo();

        indent = 0;
        this.out = out;
        generateIntro();

        for (EntityInfo entity : entityInfos) {
            generateEntityType(entity);
            generateHandlerType(entity);
        }

        generateInstancesIntro();

        for (EntityInfo entity : entityInfos) {
            generateHandlerInstance(entity);
        }

        generateInstancesOutro();
        generateOutro();

        out.flush();
    }

    private Collection<EntityInfo> loadInfo() {
        Set<EntityInfo> result = new HashSet<EntityInfo>();
        for (Map.Entry<Class, Class> entry : entityClasses.entrySet()) {
            result.add(new EntityInfo(entry.getValue(), entry.getKey(), basePackage));
        }
        return result;
    }

    private void printLine() throws IOException {
        printLine("");
    }

    private void printLine(String line) throws IOException {
        for (int i = 0; i < indent; i++) {
            out.write("  ");
        }
        out.write(line + "\n");
    }

    private void generateIntro() throws IOException {
        printLine();
    }

    private void generateOutro() throws IOException {
    }

    private void generateInstancesIntro() throws IOException {
        printLine("var " + basePackage + " = {");
        indent++;
        printLine();
        printLine("_path : \"\",");
        printLine();
        printLine("/**");
        printLine(" * @param {String} path");
        printLine(" */");
        printLine("init : function (path) {");
        printLine("  MAF._path = path;");
        printLine("},");
        printLine();
    }

    private void generateInstancesOutro() throws IOException {
        printLine("__end : null");
        printLine();
        indent--;
        printLine("}");
        printLine();
    }

    private void generateEntityType(EntityInfo entity) throws IOException, JSONException {
        JSONTree tree = builder.build(entity.getEntity());
        JSONObject jObj = (JSONObject) tree.getRoot();
        jObj = (JSONObject) jObj.get(jObj.getNames().iterator().next());

        printLine("function " + entity.getEntityTypeName() + "() {");
        indent++;

//        for (String name : jObj.getNames()) {
//            printLine("var _" + name + " = null;");
//        }
        printLine("var _data = " + (debug ? "this" : "{}") + ";");
        printLine();
        for (String name : jObj.getNames()) {
            printLine("this.set" + upperFirst(name) + " = function (value) {");
            printLine("  _data." + name + " = value;");
            printLine("}");
            printLine("this.get" + upperFirst(name) + " = function () {");
            printLine("  return _data." + name + ";");
            printLine("}");
        }

        printLine();
        printLine("if (arguments.length == 1 && (typeof arguments[0]) == \"object\") {");
        indent++;
        printLine("var d = arguments[0];");
        for (String name : jObj.getNames()) {
            printLine("_data." + name + " = d." + name + ";");
        }
        indent--;
        printLine("}");

        indent--;
        printLine("}");
        printLine();
    }

    private String upperFirst(String value) {
        return value.substring(0, 1).toUpperCase() + value.substring(1);
    }

    private void generateHandlerType(EntityInfo entity) throws IOException {
        printLine("function " + entity.getHandlerTypeName() + "() {");
        indent++;

        RequestInfo load = entity.getLoadRequest();
        if (load != null) {
            printLine("/**");
            String methodParams = null;
            for (Map.Entry<String, String> param : load.getJSParams().entrySet()) {
                methodParams = (methodParams == null ? "" : (methodParams + ", ")) + param.getKey();
                printLine(" * @param {" + param.getValue() + "} " + param.getKey());
            }
            printLine(" * @param {Function} _callback");
//            printLine(" * @return {" + entity.getEntityTypeName() + "}");
            printLine(" */");
            printLine("this.load = function (" + methodParams + ", _callback) {");
            indent++;

            printLine("dojo.xhrGet( {");
            indent++;
            printLine("url: MAF._path + " + load.getJSPath() + " + \".json\",");
            printLine("handleAs: \"json\",");
            printLine("load: function (response, ioArgs) {");
            printLine("  _callback(new " + entity.getEntityTypeName() + "(response));");
            printLine("},");
            printLine("error: function (response, ioArgs) {");
            printLine("  console.error(response, ioArgs);");
            printLine("}");
            indent--;
            printLine("});");

            indent--;
            printLine("}");
        }

        indent--;
        printLine("}");
        printLine();
    }

    private void generateHandlerInstance(EntityInfo entity) throws IOException {
        printLine(entity.getHandlerInstanceName() + " : new " + entity.getHandlerTypeName() + "(),");
        printLine();
    }

}
